﻿using System;
using System.Collections.Generic;
using System.Text;

using System.Collections;
using System.IO;
using System.Threading;
using System.Security.Cryptography;
using System.Runtime.Serialization.Formatters.Binary;
using System.Diagnostics;

namespace DirectoryChecker
{
    class Worker : IDisposable
    {
        public event Notify_Delegate OnSnapshootShow;
        public event Notify_Delegate OnSnapshootComplete;
        public event Notify_Delegate OnCheckShow;
        public event Notify_Delegate OnCheckComplete;

        private WorkOption wo;

        private int tc = 0;
        private int totalFiles = 0;
        private int errorFiles = 0;

        private ManualResetEvent mre = new ManualResetEvent(false);

        private System.Timers.Timer timer = new System.Timers.Timer();
        private int ticket = Config.MAX_TICKET;

        private Hashtable ht = new Hashtable();

        public Worker()
        {
            timer.Interval = 100;
            timer.AutoReset = true;
            timer.Elapsed += (ts, te) =>
            {
                if (ticket < Config.MAX_TICKET)
                {
                    Interlocked.Increment(ref ticket);
                }
            };
        }

        public void startCheck(object obj)
        {
            try
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();

                wo = (WorkOption)obj;
                startSnapshoot(wo);

                int a = 0, d = 0, m = 0;

                Hashtable snapshoot = loadSnapshoot();
                foreach (DictionaryEntry de1 in snapshoot)
                {
                    if (!ht.ContainsKey(de1.Key))
                    {
                        if (OnCheckShow != null)
                        {
                            d++;
                            OnCheckShow("删除: " + de1.Key);
                        }
                        continue;
                    }
                    FileSnapshoot current = (FileSnapshoot)ht[de1.Key];
                    FileSnapshoot old = (FileSnapshoot)de1.Value;
                    if (wo.Size && current.Size != old.Size)
                    {
                        if (OnCheckShow != null)
                        {
                            m++;
                            OnCheckShow("修改: " + de1.Key + " (" + old.Size.ToString() + " -> " + current.Size.ToString() + ")");
                        }
                        continue;
                    }
                    if (wo.Md5 && current.Md5 != old.Md5)
                    {
                        if (OnCheckShow != null)
                        {
                            m++;
                            OnCheckShow("修改: " + de1.Key + " (" + old.Md5 + " -> " + current.Md5 + ")");
                        }
                        continue;
                    }
                }
                foreach (DictionaryEntry de2 in ht)
                {
                    if (!snapshoot.ContainsKey(de2.Key))
                    {
                        if (OnCheckShow != null)
                        {
                            a++;
                            OnCheckShow("添加: " + de2.Key);
                        }
                        continue;
                    }
                }

                sw.Stop();
                string info = "校验完成，用时：" + sw.Elapsed.TotalSeconds.ToString() + "秒，计数：A" + a.ToString() + "-D" + d.ToString() + "-M" + m.ToString() + "";
                if (OnCheckComplete != null)
                {
                    OnCheckComplete(info);
                }

                Console.WriteLine(info);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        public void startSnapshoot(object obj)
        {
            try
            {
                Stopwatch sw = new Stopwatch();
                sw.Start();

                wo = (WorkOption)obj;
                timer.Start();
                tc = 0;
                totalFiles = 0;
                ht.Clear();
                getFiles(wo.Path);
                if (totalFiles > 0)
                {
                    mre.WaitOne();
                }
                timer.Stop();
                if (wo.Save)
                {
                    saveSnapshoot();
                }

                sw.Stop();
                string info = "快照完成，用时：" + sw.Elapsed.TotalSeconds.ToString() + "秒，文件：" + totalFiles + "，错误：" + errorFiles.ToString();
                if (OnSnapshootComplete != null)
                {
                    OnSnapshootComplete(info);
                }

                Console.WriteLine(info);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }

        private void getFiles(string path)
        {
            DirectoryInfo dir = new DirectoryInfo(path);

            //当前目录下所有子目录下的全部文件
            foreach (DirectoryInfo d in dir.GetDirectories())
            {
                getFiles(path + @"\" + d.Name);
            }

            //当前目录下所有文件（不包括子目录）
            foreach (FileInfo f in dir.GetFiles())
            {
                string fileName = path + @"\" + f.Name;
                if (!(fileName == wo.ExeFileName || fileName == wo.DataFileName))
                {
                    totalFiles++;
                    FileSnapshoot fs;
                    fs.IsFile = true;
                    fs.Name = fileName;
                    fs.Size = wo.Size ? f.Length : 0;
                    fs.Md5 = "";
                    Interlocked.Increment(ref tc);
                    mre.Reset();
                    ThreadPool.QueueUserWorkItem(new WaitCallback(snapshootFile), fs);
                }
            }

            //当前目录加入快照
            totalFiles++;
            FileSnapshoot fs2;
            fs2.IsFile = false;
            fs2.Name = path;
            fs2.Size = 0;
            fs2.Md5 = "";
            Interlocked.Increment(ref tc);
            mre.Reset();
            ThreadPool.QueueUserWorkItem(new WaitCallback(snapshootFile), fs2);

        }

        private void snapshootFile(object obj)
        {
            try
            {
                FileSnapshoot fs = (FileSnapshoot)obj;
                string key = fs.Name.Substring(wo.Path.Length);
                if (OnSnapshootShow != null && ticket > 0)
                {
                    Interlocked.Decrement(ref ticket);
                    OnSnapshootShow(key);
                }
                if (fs.IsFile && wo.Md5)
                {
                    fs.Md5 = getFileMD5(fs.Name);
                }
                //删除完整文件名，减小快照文件大小
                fs.Name = null;
                lock (ht.SyncRoot)
                {
                    ht.Add(key, fs);
                }
            }
            catch (Exception ex)
            {
                Interlocked.Increment(ref errorFiles);
                Console.WriteLine(ex.ToString());
            }
            if (Interlocked.Decrement(ref tc) == 0)
            {
                mre.Set();
            }
        }

        private string getFileMD5(string fileName)
        {
            StringBuilder sb = new StringBuilder();
            using (FileStream fs = new FileStream(fileName, FileMode.Open))
            {
                MD5 md5 = new MD5CryptoServiceProvider();
                byte[] retVal = md5.ComputeHash(fs);
                for (int i = 0; i < retVal.Length; i++)
                {
                    sb.Append(retVal[i].ToString("x2"));
                }
            }
            return sb.ToString();
        }

        private void saveSnapshoot()
        {
            FileStream fs = new FileStream(wo.DataFileName, FileMode.Create);
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(fs, ht);
            fs.Close();
        }

        private Hashtable loadSnapshoot()
        {
            Hashtable temp = new Hashtable();
            FileStream fs = new FileStream(wo.DataFileName, FileMode.Open);
            BinaryFormatter bf = new BinaryFormatter();
            temp = (Hashtable)bf.Deserialize(fs);
            fs.Close();
            return temp;
        }

        bool _disposed;
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        ~Worker()
        {
            Dispose(false);
        }
        protected virtual void Dispose(bool disposing)
        {
            if (_disposed) return;
            if (disposing)
            {
                //TODO:释放那些实现IDisposable接口的托管对象
                mre.Close();
                timer.Close();
            }
            //TODO:释放非托管资源，设置对象为null
            ht = null;
            _disposed = true;
        }
    }
}
